成品連結:Countdown Timer、HTML 程式碼、完成後 JS 程式碼
(今天沒有完成前 JS 程式碼,自己創一個吧!但記得 script 的連結要改)
今天要做的是時間倒數器,有預設幾種選項,或是讓使用者指定時間。話不多說,我們開始吧!
首先列出待辦事項,等等再一一解決:
要做出倒數功能,首先要先取得目前時間戳(timestamp),然後將指定的時間長度+取得的時間戳,最後再使用 setInterval(..)
每秒更新。
看起來或許有點負責複雜,我們一個步驟一個來吧!
其實也不一定要用時間戳,你也可以取得時、分、秒再全部轉成秒數,但使用時間戳會相對不麻煩,所以這裡會使用此方法。
我們宣吿一個 function timer
來處理倒數的功能,並會帶入秒數作為參數
function timer(seconds) {
// code here
}
接著取得目前時間戳
function timer(seconds) {
const now = Date.now();
}
接著將設定的秒數加上 now
作為結束的時間,這裡要注意時間戳是使用毫秒,所以要記得將引數 seconds
乘以 1000
function timer(seconds) {
const now = Date.now();
const then = Date.now() + seconds * 1000;
}
setInterval(..)
每秒更新使用 setInterval
設定每 1000 毫秒更新,但要如何更新?我們可以透過每秒將 then
(有就是結束時間的時間戳)扣掉目前的時間戳並轉成秒,直到倒數到 0 為止
function timer(seconds) {
const now = Date.now();
const then = Date.now() + seconds * 1000;
setInterval(() => {
const secondsLeft = Math.round((then - Date.now()) / 1000) // Math.round() 可以四捨五入並取整數部分
// 檢查是否需要停止倒數計時(當數字為 0 時)
// 將時間渲染至畫面
}, 1000);
}
要注意的是當倒數秒數為 0 時要停止 setInterval
,如果是寫下方的 code:
if (secondsLeft < 0) {
return;
}
這樣的確會讓計時停止,但其實 setInterval
function 本身並沒有停止,如果要停止需要執行另一個 function clearInterval
來清除 setInterval
要使用 clearInterval
我們樣先將 setInterval
存入一變數 countdown
,等等才能使用 clearInterval
停止
let countdown;
function timer(seconds) {
const now = Date.now();
const then = Date.now() + seconds * 1000;
countdown = setInterval(() => {
const secondsLeft = Math.round((then - Date.now()) / 1000) // Math.round() 可以四捨五入並取整數部分
// 檢查是否需要停止倒數計時(當數字為 0 時)
if (secondsLeft < 0) {
clearInterval(countdown);
return; // return 會跳出 function
}
// 將時間渲染至畫面
}, 1000);
}
最後要將時間渲染至畫面,可以看到 HTML 上已有兩個元素 h1.display__time-left
、p.display__end-time
分別要顯示倒數時間以及結束的時間
我們另外宣告一個 function 來處理時間2倒數,這個 function 會帶入一個秒數的引數
由於我希望能以分、秒的方式顯示在畫面上,所以要先設定分、秒的值
const timeLeft = document.querySelector('.display__time-left');
const endTIme = document.querySelector('.display__end-time');
function displayTimeLeft(seconds) {
let minutes = Math.floor(seconds / 60);
let remainderSeconds = seconds % 60;
const display = `${minutes}:${remainderSeconds}`;
}
並把它加到 timer
當中執行
function timer(seconds) {
const now = Date.now();
const then = Date.now() + seconds * 1000;
countdown = setInterval(() => {
const secondsLeft = Math.round((then - Date.now()) / 1000) // Math.round() 可以四捨五入並取整數部分
// 檢查是否需要停止倒數計時(當數字為 0 時)
if (secondsLeft < 0) {
clearInterval(countdown);
return; // return 會跳出 function
}
// 將時間渲染至畫面
displayTimeLeft(secondsLeft);
}, 1000);
}
這裡會發現當印出時比如是 20 秒,一開始並不會顯示 00:20
而是 00:19
,這是因為 setInterval
過了一秒才執行,而顯示 00:20
的時機已經過了,因此要在 setInterval
開始前先執行一次 displayTimeLeft(..)
function timer(seconds) {
const now = Date.now();
const then = Date.now() + seconds * 1000;
displayTimeLeft(seconds);
countdown = setInterval(() => {
const secondsLeft = Math.round((then - Date.now()) / 1000) // Math.round() 可以四捨五入並取整數部分
// 檢查是否需要停止倒數計時(當數字為 0 時)
if (secondsLeft < 0) {
clearInterval(countdown);
return; // return 會跳出 function
}
// 將時間渲染至畫面
displayTimeLeft(secondsLeft);
}, 1000);
}
如果你試著印出結果,會發現有用,但如果分或秒小於 10 的時候並不會如長的的時鐘顯示如 09:08
的格式而是 9:8
。要解決這問題我們要多做一些判斷
function displayTimeLeft(seconds) {
let minutes = Math.floor(seconds / 60);
let remainderSeconds = seconds % 60;
if (minutes < 10) {
minutes = '0' + minutes;
}
if (remainderSeconds < 10) {
remainderSeconds = '0' + remainderSeconds;
}
const display = `${minutes}:${remainderSeconds}`;
}
接著要印出結束時間,也就是顯示如 Be Back At 09:33
的格式。如上,我們新建一個 function displayEndTime
來處理這個動作,並帶入一個時間戳的參數(也就是完成時間戳 then
)
function displayEndTime(timestamp) {
// code here
}
由於只是要顯示完成時間,所以不同於更新剩餘時間,這個 function 並不需要在 setInterval
中執行,只需要執行一次即可。
在 displayEndTime
當中不需要顯示秒數,所以我們只要算出時、發、分就好
function displayEndTime(timestamp) {
const time = new Date(timestamp);
const hours = time.getHours();
const minutes = time.getMinutes();
}
new Date(timestamp)
可以將時間戳轉換成我們熟悉的時間顯示方式。例如 1541698934495
就是 Fri Nov 09 2018 01:42:14 GMT+0800 (台北標準時間)
與 displayTimeLeft
相同,我希望時、分的顯示方式是 08:21
般的格式,所以需要利用 if...else
做判斷。但這裡稍微用不同方式,利用三元運算是判斷。
function displayEndTime(timestamp) {
const time = new Date(timestamp);
const hours = time.getHours();
const minutes = time.getMinutes();
const display = `Be Back At ${hours < 10 ? '0' : ''}${hours}:${minutes < 10 ? '0' : ''}${minutes}`;
endTIme.textContent = display;
}
三元運算式的判斷方式是當時或分小於 10 的時候在數字前面加上 string '0'
,若大於 10 則加上空 string ''
這裡其實有隱含強制轉型的發生。當目前「時」是 7 的時候,number
7
前面會加上 string'0'
,這時候7
會被強制轉型成 string'7'
所以相加後才會是 string'07'
,但這不是今天主題故不做太多討論。
最後將 displayEndTime
放到 timer
中執行
function timer(seconds) {
const now = Date.now();
const then = Date.now() + seconds * 1000;
displayTimeLeft(seconds);
displayEndTime(then);
countdown = setInterval(() => {
const secondsLeft = Math.round((then - Date.now()) / 1000) // Math.round() 可以四捨五入並取整數部分
// 檢查是否需要停止倒數計時(當數字為 0 時)
if (secondsLeft < 0) {
clearInterval(countdown);
return; // return 會跳出 function
}
// 將時間渲染至畫面
displayTimeLeft(secondsLeft);
}, 1000);
}
到這裡大概的功能完成了,我們來綁定預設的時間吧!
首先是大家熟悉的事件綁定
const buttons = document.querySelectorAll('button[data-time]');
function startTimer() {
// code here
}
buttons.forEach(button => button.addEventListener('click', startTimer));
callback 中需要做的其實就是將 dataset 中的秒數取出並執行 timer
function startTimer() {
const seconds = parseInt(this.dataset.time); // parseInt 取出數字的整數部分
timer(seconds);
}
到這裡點擊按鈕會正常顯示倒數以及結束時間了!但是...
當已有一個倒數計時器而你再點擊另一個時間,會發現倒數不斷在各倒數器間跳動。這是因為我們實際上也因為 setInterval
同時執行了好幾個倒數計時器,也因此我們需要在 timer
執行時先執行 clearInterval
function timer(seconds) {
// 清除已有的倒數計時器
clearInterval(countdown);
const now = Date.now();
const then = Date.now() + seconds * 1000;
displayTimeLeft(seconds);
displayEndTime(then);
countdown = setInterval(() => {
const secondsLeft = Math.round((then - Date.now()) / 1000) // Math.round() 可以四捨五入並取整數部分
// 檢查是否需要停止倒數計時(當數字為 0 時)
if (secondsLeft < 0) {
clearInterval(countdown);
return; // return 會跳出 function
}
// 將時間渲染至畫面
displayTimeLeft(secondsLeft);
}, 1000);
}
最後就是 input 輸入時間了。與按鈕一樣,我們先綁定事件。在 HTML 當中 input
是被 form
包著的,所以事件我們使用 form 的 submit
const custom = document.querySelector('#custom');
function startCustomTime(e) {
// code here
}
custom.addEventListener('submit', startCustomTime);
由於 form
submit 之後會提交表單重整頁面,但我們並不需要,因此要先 event.preventDefault()
來停止預設行為
而當使用者輸入分鐘數時我們要先轉換成秒數,並執行 timer
,最後使用表單的 method event.reset()
來重置表單
function startCustomTime(e) {
e.preventDefault();
const minutes = parseInt(this.minutes.value); // 使用 this.minutes.value 取得 input 的值
timer(minutes * 60); // 轉成秒數後再執行 timer
this.reset();
}
到這裡基本上就完成今天的作品了,但我想再完善一下篩選使用者輸入的分鐘數
function startCustomTime(e) {
e.preventDefault();
const minutes = parseInt(this.minutes.value); // 使用 this.minutes.value 取得 input 的值
if ((0 / minutes) !== 0 || minutes > 1440) { // 1440 minutess = 1 day
clearInterval(countdown);
timeLeft.textContent = (0 / minutes) !== 0 ? 'Numbers only!' : 'Too long!';
endTIme.textContent = '';
return;
}
timer(minutes * 60); // 轉成秒數後再執行 timer
this.reset();
}
當輸入分鐘數不是 number 時畫面顯示「Numbers only!」;當輸入分鐘數大於 1440 時顯示「Too long!」
恭喜完成!明天就是最後一天了!